import 'package:flutter/material.dart';

mixin AutoScalableTextMixin {
  double getTextHeight({
    required String text,
    required TextStyle style,
    required double textScaleFactor,
  }) {
    final textSpan = TextSpan(
      style: style,
      text: text,
    );

    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
      textScaleFactor: textScaleFactor,
      maxLines: 1,
    );

    textPainter.layout();

    return textPainter.height;
  }

  double calculateFontSize({
    required String text,
    required BoxConstraints constraints,
    required TextStyle style,
    required int maxLines,
    required double textScaleFactor,
    double stepGranularity = 0.5,
    double minFontSize = 12,
  }) {
    const maxFontSize = double.infinity;

    final span = TextSpan(
      style: style,
      text: text,
    );

    final defaultFontSize = style.fontSize!.clamp(minFontSize, maxFontSize);
    final defaultScale = defaultFontSize * textScaleFactor / style.fontSize!;
    if (checkTextFits(
      textSpan: span,
      constraints: constraints,
      textScaleFactor: defaultScale,
      maxLines: maxLines,
    )) {
      return defaultFontSize * textScaleFactor;
    }

    var left = (minFontSize / stepGranularity).floor();
    var right = (defaultFontSize / stepGranularity).ceil();

    var lastValueFits = false;
    while (left <= right) {
      final mid = (left + (right - left) / 2).toInt();
      double scale;
      scale = mid * textScaleFactor * stepGranularity / style.fontSize!;
      if (checkTextFits(
        textSpan: span,
        constraints: constraints,
        textScaleFactor: scale,
        maxLines: maxLines,
      )) {
        left = mid + 1;
        lastValueFits = true;
      } else {
        right = mid - 1;
      }
    }

    if (!lastValueFits) {
      right += 1;
    }

    return right * textScaleFactor * stepGranularity;
  }

  bool checkTextFits({
    required TextSpan textSpan,
    required BoxConstraints constraints,
    required int maxLines,
    required double textScaleFactor,
  }) {
    final words = textSpan.toPlainText().split(RegExp(r'\s+'));

    final wordWrapTextPainter = TextPainter(
      text: TextSpan(
        style: textSpan.style,
        text: words.join('\n'),
      ),
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
      textScaleFactor: textScaleFactor,
      maxLines: words.length,
    );

    wordWrapTextPainter.layout(
      maxWidth: constraints.maxWidth,
    );

    if (wordWrapTextPainter.didExceedMaxLines || wordWrapTextPainter.width > constraints.maxWidth) {
      return false;
    }

    final textPainter = TextPainter(
      text: textSpan,
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
    );

    textPainter.layout(
      maxWidth: constraints.maxWidth,
    );

    if (textPainter.didExceedMaxLines) {
      return false;
    }

    if (textPainter.height > constraints.maxHeight || textPainter.width > constraints.maxWidth) {
      return false;
    }

    return true;
  }
}
